package com.jeremyaburns.oddio 
{

	public class Track 
	{
		private var notes   :Vector.<TrackNote>;
		private var volumes :Vector.<TrackControl>;
		private var control :Vector.<TrackControl>;
		
		public var instrument :Instrument;
		
		public function Track() 
		{
			this.notes   = new Vector.<TrackNote>();
			this.volumes = new Vector.<TrackControl>();
			this.control = new Vector.<TrackControl>();
			
			this.instrument = null;
			this.curNote    = null;
			this.nextNote   = null;
		}
		
//== Editing interface =========================================================================================
		
		// Adds an note to the track, placing it in the proper place in the 'notes' array.
		public function addNote( note:TrackNote ) :void
		{
			var i:int;
			for( i = 0 ; i < notes.length ; ++i )
			{
				if( notes[i].timeIndex == note.timeIndex )
				{
					notes[i] = note; // Replace the note at the current time index if one already exists.
					return;
				}
				else if( notes[i].timeIndex > note.timeIndex ) break;
			}
			notes.splice( i, 0, note );
		}
		
		public function removeAllNotes() :void
		{
			notes.splice( 0, notes.length );
		}
				
//== Playback interface =========================================================================================

		private var position    :int;
		private var noteIndex   :int;
		private var curNote     :TrackNote;
		private var nextNote    :TrackNote;		
		private var decayLength :int;
		private var mFinished   :Boolean;

		// Playback is done by first calling this function with the desired tempo, and
		// then making a series of calls to getNextSample until that function returns false.
		internal function prepareForPlayback( tempo:int ) :void
		{
			// Prepare all the TrackItems for playback.
			var i:TrackItem;
			for each( i in notes   ) i.prepareForPlayback( tempo );
			for each( i in volumes ) i.prepareForPlayback( tempo );
			for each( i in control ) i.prepareForPlayback( tempo );
			
			// Reset the playhead.
			noteIndex = 2;
			curNote   = notes[0];
			nextNote  = ( notes.length >= 2 ) ? notes[1] : null ;
			mFinished = false;
			position  = 0;
			
			// Decay linearly for the last half of a single note.
			decayLength = tempo/2;
		}
		
		// Move the playhead forwards one sample and read in to the parameter.
		// Returns true if the track still has something to contribute.
		internal function addNextSample( output:TrackSample ) :Boolean
		{
			if( mFinished ) return false;
			
			position++;
			
			var sample      :Number  = 0;
			var moveNext    :Boolean = false;
			var fetchSample :Boolean = true;
		
			// Check to see if we're playing a note on this track.
			if( position >= curNote.timeIndexInSamples )
			{
				// If the next note needs to start, move to the next note and fetch a sample.
				if( nextNote && position >= nextNote.timeIndexInSamples ) {
					moveNext    = true;
					fetchSample = true;
				}
				
				// If this note is over, move to the next note, but don't need to fetch a sample. There's nothing there.
				else if( position >= curNote.endIndexInSamples ) {
					if( nextNote == null ) {
						mFinished = true;
						return false;
					} else {
						moveNext    = true;
						fetchSample = false;
					}
				}
				
				// Move to the next note.
				if( moveNext ) {
					curNote  = nextNote;
					nextNote = ( noteIndex >= notes.length ) ? null : notes[ noteIndex++ ];
				}
				
				// Use the track's instrument to get a sample if we need to.
				if( fetchSample )
				{
					// Get the sample using the track's instrument.
					var relativeTime :int = position - curNote.timeIndexInSamples;
					sample = instrument.getSample( relativeTime, curNote.samplesPerWave );
					
					// Apply the decay if we're near the end of the note.
					var decayStart :int = curNote.endIndexInSamples - decayLength;
					if( position >= decayStart ) {
						sample *= 1.0 - Number( position - decayStart ) / decayLength;
					}
					
					// Add the sample to the output.
					output.left  += sample;
					output.right += sample;
				}
			}
			
			return true;
		}
		
//===============================================================================================================
		
	}
}